<?php


namespace App\Repositories\Gashapon;

use App\Exceptions\BasicException;
use App\Models\MainDB\GiftBackpack;
use App\Repositories\GiftRepository;
use App\Services\Tools\PushChatService;
use App\Services\Tools\PushService;
use App\Services\Tools\RedisService;
use App\Traits\Singleton;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\DB;

class LotteryRepository
{
    use Singleton;

    /**
     * @throws \App\Exceptions\BasicException
     */
    public function draw(int $userId, int $count)
    {
        DB::beginTransaction();
        try {
            $setting    = SettingRepository::getInstance()->setting(true);
            $drawOutput = 0;
            $drawInput  = $count * $setting->balance_coin;
            $user       = UserRepository::getInstance()->drawUser($setting, $userId, true);
            // 验证余额
            if ($user->balance < $count) {
                throw new BasicException(-1, '余额不足');
            }
            // 获取用户抽取奖池
            $userPool           = PoolRepository::getInstance()->getUserPool($user, true, true);
            $luckyPrizeInterval = $userPool->lucky_prize_interval; // 最大礼物区间
            if ($luckyPrizeInterval <= 0) {
                $luckyPrizeInterval = mt_rand(min($setting->lucky_prize_min_interval, $setting->lucky_prize_max_interval), max($setting->lucky_prize_min_interval, $setting->lucky_prize_max_interval));
            }
            $luckyScore = $userPool->lucky_score;                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          // 当前幸运值
            // 奖池奖品
            $poolPrizes      = PoolPrizeRepository::getInstance()->getPoolPrize($userPool->id);
            $poolPrizesCount = $poolPrizes->where('count', '>', 0)->sum('count');
            // 抽奖
            $drawSetting                       = Collection::make([]);                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          // 抽奖设置
            $drawSetting->special_prize_coin = $setting->special_prize_coin;                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              // 特殊礼物价值
            $drawSetting->lucky_prize_coin   = $setting->lucky_prize_coin;                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                // 幸运礼物价值
            $drawSetting->lucky_score        = $luckyScore;                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               // 幸运值
            $drawSetting->lucky_prize_interval = $luckyPrizeInterval;                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     // 幸运奖品间隔
            $drawPoolPrizes                    = $this->drawPrizes($user, $poolPrizes, $count, $drawSetting);
            if ($poolPrizesCount >= $count) { // 礼物充足
                $drawTemplatePrizes = [];
            } else { // 礼物不充足
                $templatePrizes        = PoolTemplateRepository::getInstance()->getPoolTemplate($userPool->id);
                $poolUpdatePrizes = $templatePrizes->pluck('count', 'prize_id')->toArray();
                $drawTemplatePrizes = $this->drawPrizes($user, $templatePrizes, $count - $poolPrizesCount, $drawSetting);
                $userPool->round    += 1;                              // 奖池轮数增加
                $userPool->prize_count = array_sum($poolUpdatePrizes); // 奖池重制, 计算本期奖池的奖品总数.
                // 创建下一轮奖品
                PoolPrizeRepository::getInstance()->createNextRounPrizes($userPool->id, $poolUpdatePrizes);
                // 上一期奖励抽空, 清空幸运值
                $luckyScore = 0;
            }
            $drawPrizes      = $this->appendPrizes($drawPoolPrizes, $drawTemplatePrizes);
            $drawPrizesCount = array_sum($drawPrizes);
            if ($drawPrizesCount != $count) {
                throw new BasicException(-1, '扭蛋机异常');
            }
            $gifts = (new GiftRepository())->giftsByIds(array_keys($drawPrizes)); // 抽中礼物信息
            if ($gifts->where('price', '>=', $setting->lucky_prize_coin)->whereIn('id', array_keys($drawPrizes))->isNotEmpty()) {
                // 抽中幸运礼物, 清空幸运值
                $luckyScore = 0;
            } else {
                $luckyScore += $drawPrizesCount; // 未抽中幸运礼物, 增加幸运值
            }
            $notifyCount      = $broadcastCount = 0;                                                                                                                                     // 总数量.(通知,广播)
            $drawPrizesRecord = [];                                                                                                                                                      // 抽取记录
            // ======================================== 记录 ========================================
            // 1. 抽奖记录
            $draw           = DrawRepository::getInstance()->create(['user_id' => $userId, 'count' => $count, 'prize_count' => $drawPrizesCount, 'input' => $drawInput, 'output' => $drawOutput]); // 抽奖记录
            $notifyPushData = $broadcastPushData = [];                                                                                                                                   // 推送数据
            foreach ($drawPrizes as $prizeId => $prizeCount) {
                $gift       = $gifts->where('id', $prizeId)->first();
                $drawOutput += $gift->price * $prizeCount;
                if ($gift->price >= $setting->notify_coin) {
                    $notifyCount       += $prizeCount;
                    $notifyGift  = Collection::make([]);
                    $notifyGift->name = $gift->name ?? '';
                    $notifyGift->price = $gift->price;
                    $notifyGift->count = $prizeCount;

                    $notifyPushData[] = $notifyGift;
                }
                if ($gift->price >= $setting->broadcast_coin) {
                    $broadcastCount += $prizeCount;

                    $broadcastGift          = Collection::make([]);
                    $broadcastGift->name = $gift->name ?? '';
                    $broadcastGift->price = $gift->price;
                    $broadcastGift->count = $prizeCount;
                    $broadcastGift->picture = $gift->picture ?? '';
                    $broadcastPushData[]    = $broadcastGift;
                }
                $drawPrizesRecord[] = ['user_id' => $userId, 'draw_id' => $draw->id, 'prize_id' => $prizeId, 'prize_price' => $gift->price, 'count' => $prizeCount,];
            }
            // 2. 余额使用日志
            BalanceLogRepository::getInstance()->draw($userId, $count, $user->balance);
            // 3. 更新抽奖产出
            $draw->output += $drawOutput;
            $draw->save();
            // 4. 抽奖详情
            DrawRecordRepository::getInstance()->insert($drawPrizesRecord);
            // 5. 更新奖池, 部分数据在替换奖池的时候设置
            $userPool->count                += $drawPrizesCount;
            $userPool->input += $drawInput;
            $userPool->output += $drawOutput;
            $userPool->lucky_prize_interval = $luckyPrizeInterval;
            $userPool->lucky_score          = $luckyScore;
            $userPool->save();
            // 6. 奖池礼物更新
            PoolPrizeRepository::getInstance()->batchDecPrizesByPoolId($userPool->id, $drawPrizes);
            // 7. 用户统计
            UserRepository::getInstance()->drawUpdate($userId, $drawInput, $drawOutput, $drawPrizesCount);
            // 8. 用户幸运统计
            if ($notifyCount > 1) {
                LuckyStatDateRepository::getInstance()->drawUpdate($userId, $notifyCount, $broadcastCount);
            }
            // 9. 用户背包
            (new GiftBackpack())->addDraws($userId, $drawPrizes);
            // 10. 减少余额, 等级变更
            unset($user->game);
            $user->input   += $drawInput;
            $user->output += $drawOutput;
            $user->count  += $drawPrizesCount;
            $user->balance -= $count;
            $user->exp     += $count;                                                                    // 用户经验
            $level         = UserRepository::getInstance()->checkNextLevelExp($user->level, $user->exp); // 用户等级
            $user->level   = $level;
            $user->save();
            DB::commit();
        } catch (\Exception $e) {
            DB::rollBack();
            throw new BasicException($e->getCode(), $e->getMessage());
        }
        // 11. 推送
        $this->push($userId, $notifyPushData, $broadcastPushData);
        return $draw;
    }

    protected function push(int $userId, $notifyPushData, $broadcastPushData)
    {
        try {
            $user = RedisService::getUserChatLabel($userId);
            foreach ($notifyPushData as $value) {
                (new PushChatService())->gashapon($user, $value);
            }
            (new PushService())->gashapon($broadcastPushData, $user);
        } catch (\Exception $e) {
            return false;
        }
        return true;
    }

    // 追加奖励
    protected function appendPrizes(array $prizes, array $append)
    {
        foreach ($append as $prizeId => $prizeCount) {
            $prizes[$prizeId] = isset($prizes[$prizeId]) ? $prizes[$prizeId] + $prizeCount : $prizeCount;
        }
        return $prizes;
    }

    // 抽取奖品
    protected function drawPrizes($user, $pool, int $count, $setting): array
    {
        if ($count <= 0) {
            return [];
        }
        $poolCount = $pool->where('count', '>', 0)->sum('count');

        if ($poolCount <= $count) {
            return $pool->where('count', '>', 0)->pluck('count', 'prize_id')->toArray();
        }
        $specialPrizes = [];
        $specialPrice  = $setting->special_prize_coin;
        if ($count > 1) {
            $specialPrizes = $this->specialSub($pool, $count, $setting); // 特殊礼物抽取
        }
        $count  -= array_sum($specialPrizes);
        $prizes = $this->sub($pool->where('count', '>', 0)->where('price', '<', $specialPrice), $count, $user->game->weight); // 普通礼物抽取
        foreach ($specialPrizes as $prizeId => $prizeCount) {
            $prizes[$prizeId] = isset($prizes[$prizeId]) ? $prizes[$prizeId] + $prizeCount : $prizeCount;
        }
        return $prizes;
    }

    /**
     * 抽取特殊礼物
     *
     * @param Collection $pool 奖池全部奖品
     * @param int        $count 抽取数量
     * @param int        $specialPrice 特殊礼物价格
     *
     * @return array
     */
    protected function specialSub(Collection $pool, int $count, $setting)
    {
        $specialPrizeCoin   = $setting->special_prize_coin;   // 特殊礼物价值
        $luckyPrizeCoin   = $setting->lucky_prize_coin;     // 幸运礼物价值
        $luckyScore       = $setting->lucky_score;          // 幸运值
        $luckyPrizeInterval = $setting->lucky_prize_interval; // 幸运奖品间隔

        $specialPrizes           = $pool->where('price', '>=', $specialPrizeCoin);       // 特殊礼物
        $luckyPrizes   = $pool->where('price', '>=', $luckyPrizeCoin);         // 幸运礼物
        $prizesCount   = $pool->where('count', '>', 0)->sum('count');          // 奖品当前剩余数量
        $prizesTotalCount = $pool->sum('total_count');                            // 奖品总数(包含被抽的数量
        $specialPrizesCount = $specialPrizes->where('count', '>', 0)->sum('count'); // 特殊礼物当前数量
        $specialPrizesTotalCount = $specialPrizes->sum('total_count');                   // 特殊礼物总数
        if ($specialPrizesCount <= 0) { // 无特殊礼物
            return [];
        }
        $interval = $prizesTotalCount / $specialPrizesTotalCount;         // 礼物间隔 = 奖品总数 / 特殊礼物数量
        // $drawStartPosition = $prizesTotalCount - $prizesCount + 1; // 本次抽取开始位置 = 奖品总数 - 奖品剩余数量 + 1
        $drawEndPosition = $prizesTotalCount - $prizesCount + $count + 1; // 本次抽取结束位置 = 奖品总数 - 奖品剩余数量 + 本次抽取数量 + 1

        $drawPrizes = []; // 抽中礼物
        // $getCount = 0; // 抽中数量(改为只抽出一个奖品 后废弃
        while ($specialPrizesCount) {
            $number        = $specialPrizesTotalCount - $specialPrizesCount + 1; // 特殊礼物序号 = 特殊礼物总数 - 特殊礼物数量 + 1
            $startPosition = $interval * ($number - 1) + 1;                      // 奖品开始区间 = 间隔 * (序号 - 1) + 1.
            $endPosition   = $interval * $number;                                // 奖品结束区间 = 间隔 * 序号
            $position      = mt_rand($startPosition, $endPosition);              // 奖品随机位置
            // if($position <= $drawEndPosition && $getCount <= $count){ // 奖品位置 < 抽取结束位置 && 抽中数量 <= 抽取数量 为抽中 (改为只抽出一个奖品 后废弃
            if ($position <= $drawEndPosition) { // 奖品位置 < 抽取结束位置 && 抽中数量 <= 抽取数量 为抽中
                // $prizeId = $specialPrizes->where('count', '>', 0)->pluck('prize_id')->random(); // 数量>0的特殊礼物随机一个
                $luckyPrizeCount = $luckyPrizes->where('count', '>', 0)->sum('count');
                if ($luckyScore >= $luckyPrizeInterval && $luckyPrizeCount > 0) {
                    $prizeId = $pool->where('count', '>', 0)
                        ->where('price', '>=', $luckyPrizeCoin)
                        ->pluck('prize_id')
                        ->random();
                } else {
                    $specialPrizeCanDrawCount = $pool->where('count', '>', 0)
                        ->where('price', '<', $luckyPrizeCoin)
                        ->where('price', '>=', $specialPrizeCoin)->sum('count');
                    if ($specialPrizeCanDrawCount > 0) {
                        $prizeId = $pool->where('count', '>', 0)
                            ->where('price', '<', $luckyPrizeCoin)
                            ->where('price', '>=', $specialPrizeCoin)
                            ->pluck('prize_id')
                            ->random();
                    }
                }

                if (isset($prizeId)) {
                    $drawPrizes[$prizeId] = isset($drawPrizes[$prizeId]) ? $drawPrizes[$prizeId] + 1 : 1;
                    $prize                = $specialPrizes->where('prize_id', $prizeId)->first();
                    $prize->count         -= 1;
                    // $getCount++; (改为只抽出一个奖品 后废弃
                    break; // 抽中一个跳出循环
                }
            }
            $specialPrizesCount--;
        }
        return $drawPrizes;
    }

    /**
     * 抽取
     *
     * @param Collection $pool 奖池奖品, 不包含特殊奖品
     * @param int        $count 抽取数量
     * @param int        $weight 用户权重
     *
     * @return array
     */
    protected function sub(Collection $pool, int $count, int $weight = 0): array
    {
        if ($count <= 0) {
            return [];
        }
        $start     = 0;
        $pool  = $pool->sortBy('price')->transform(function ($item) use (&$start) {
            $item->start = $start;                // 奖品开始位置
            $item->end   = $start + $item->count; // 奖品结束位置
            $start       = $item->end + 1;
            return $item;
        })->sortBy('start');
        $poolCount = $pool->sum('count');
        $prizes    = [];
        while ($count) {
            // "======================================== 第x次抽 ========================================"
            $min   = 0;
            $max = $poolCount - 1;
            $index = mt_rand($min, $max) + $weight; // 随机位置(0, 奖池数量-1) + 用户权重
            $count--;

            $prize = $pool->where('start', '<=', $index)->where('end', '>=', $index)->where('count', '>', 0)->first();
            if (!$prize) { // 元素不存在，获取开始值大于 并且 数量 > 0 的奖品
                $prize = $pool->where('start', '>=', $index)->where('count', '>', 0)->first();
                if (!$prize) { // 元素依然不存在, 取最后一个元素
                    $prize = $pool->where('count', '>', 0)->last();
                }
            }
            $prize->count     -= 1;
            $prizeId      = $prize->prize_id;
            $prizes[$prizeId] = isset($prizes[$prizeId]) ? $prizes[$prizeId] + 1 : 1;
        }
        return $prizes;
    }

}